package org.diy.redisson.acpects;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.diy.redisson.annotation.Idempotent;
import org.diy.redisson.annotation.IdempotentField;
import org.diy.redisson.inters.IdmpotentInter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * 局限幂等切面 {@link Idempotent,IdempotentField}
 * @author tmz
 * @date 2021/7/20
 */
@Aspect
public class IdempotentAcpect {
    private final Logger log = LoggerFactory.getLogger(IdempotentAcpect.class);
    private static final String preKey = "Medlive_Cotro_";
    private RedissonClient redissonClient;
    private static final String SPACE = " ";

    public IdempotentAcpect(RedissonClient redissonClient) {
        Assert.notNull(redissonClient, "RedissonClient isNull");
        this.redissonClient = redissonClient;
    }

    @Pointcut("execution(public org.diy.redisson.entity.Result com..controller..*.*(..)) && @annotation(org.diy.redisson.annotation.Idempotent)")
    public void putMethod() {
        log.info("进来了");

    }


    @Around("putMethod()")
    public Object doMethodAround(JoinPoint jp) throws Throwable {
        ProceedingJoinPoint point = (ProceedingJoinPoint) jp;
        MethodSignature signature = (MethodSignature) jp.getSignature();
        //获取注解上的值
        Idempotent anno = signature.getMethod().getAnnotation(Idempotent.class);
        String lockKeyStr = lockKeyStr(jp);
        RLock lock = redissonClient.getFairLock(lockKeyStr);
        if (!lock.isLocked()) {
            lock(lockKeyStr, anno.time(), anno.timeUnit());
            return point.proceed();
        }
        return anno.defaultReturn().getResult();
    }

    public String lockKeyStr(JoinPoint jp) {
        ProceedingJoinPoint point = (ProceedingJoinPoint) jp;
        MethodSignature signature = (MethodSignature) jp.getSignature();
        //获取注解上的值
        Idempotent anno = signature.getMethod().getAnnotation(Idempotent.class);
        if (anno == null) {
            return null;
        }
        Object[] args = point.getArgs();
        StringBuilder sub = new StringBuilder(signature.getMethod().getName());
        if (StringUtils.isNotBlank(anno.pre())) {
            sub.append(SPACE).append(anno.pre());
        }
        if (StringUtils.isNotBlank(anno.value())) {
            sub.append(SPACE).append(anno.value());
        }
        if (args.length > 0) {
            for (Object arg : args) {
                if (arg instanceof IdmpotentInter) {
                    sub.append(SPACE);
                    switch (anno.strategy()) {
                        case LIST_PARAMETER:
                            sub.append(getGsonContant().toJson(arg));
                            break;
                        case USE_DTO_FIELD:
                            Arrays.stream(arg.getClass().getDeclaredFields())
                                    .filter(field -> field.getAnnotation(IdempotentField.class) != null)
                                    .forEach(field -> {
                                        try {
                                            if (!field.isAccessible()) {
                                                field.setAccessible(true);
                                            }
                                            sub.append(getGsonContant().toJson(field.get(arg)));
                                        } catch (Exception e) {
                                            e.printStackTrace();
                                        }
                                    });
                            break;
                    }
                    break;
                }
            }
        }
        return String.format("%s%s", preKey, sub);
    }

    @AfterThrowing(pointcut = "putMethod()", throwing = "ex")
    public void err(JoinPoint jp, Throwable ex) throws Throwable {
        MethodSignature signature = (MethodSignature) jp.getSignature();
        Idempotent anno = signature.getMethod().getAnnotation(Idempotent.class);
        Class<? extends Throwable>[] unlockForErrs = anno.unlockForErr();
        String lockKeyStr = lockKeyStr(jp);
        if (Arrays.stream(unlockForErrs)
                .anyMatch(item -> {
                    boolean f = item.getName().equals(ex.getClass().getName());
                    return f;
                })) {
            log.info("aop_err_Idempotent,因异常解锁");
            unlock(lockKeyStr);
        }
    }

    void unlock(String key) throws ExecutionException, InterruptedException {
        RLock lock = redissonClient.getFairLock(key);
        lock.unlockAsync().get();
    }

    void lock(String key, long time, TimeUnit unit) {
        RLock lock = redissonClient.getFairLock(key);
        lock.lock(time, unit);
    }
    Gson getGsonContant(){
        return new GsonBuilder().create();
    }
}
